Once you configure your types to participate in the .NET serialization scheme by applying the necessary attributes, your next step is to choose which format (binary, SOAP, or XML) you should use when persisting your object’s state. Each possibility is represented by the following classes:
The BinaryFormatter type serializes your object’s state to a stream using a compact binary format. This type is defined within the System.Runtime.Serialization.Formatters.Binary namespace that is part of mscorlib.dll. If you wish to gain access to this type, you can specify the following C# using directive:
// Gain access to the BinaryFormatter in mscorlib.dll. using System.Runtime.Serialization.Formatters.Binary;
The SoapFormatter type persists an object’s state as a SOAP message (the standard XML format for passing messages to/from a web service). This type is defined within the System.Runtime.Serialization.Formatters.Soap namespace, which is defined in a separate assembly. Thus, to format your object graph into a SOAP message, you must first set a reference to System.Runtime.Serialization.Formatters.Soap.dll using the Visual Studio 2010 Add Reference dialog box and then specify the following C# using directive:
// Must reference System.Runtime.Serialization.Formatters.Soap.dll. using System.Runtime.Serialization.Formatters.Soap;
Finally, if you wish to persist a tree of objects as an XML document, you can use the XmlSerializer type. To use this type, you need to specify that you are using the System.Xml.Serialization namespace and set a reference to the assembly System.Xml.dll. As luck would have it, all Visual Studio 2010 project templates automatically reference System.Xml.dll; therefore, all you need to do is use the following namespace:
// Defined within System.Xml.dll. using System.Xml.Serialization;
Regardless of which formatter you choose to use, be aware that all of them derive directly from System.Object, so they do not share a common set of members from a serialization-centric base class. However, the BinaryFormatter and SoapFormatter types do support common members through the implementation of the IFormatter and IRemotingFormatter interfaces (strange as it might seem, the XmlSerializer implements neither).
System.Runtime.Serialization.IFormatter defines the core Serialize() and Deserialize() methods, which do the grunt work to move your object graphs into and out of a specific stream. Beyond these members, IFormatter defines a few properties that the implementing type uses behind the scenes:
public interface IFormatter { SerializationBinder Binder { get; set; } StreamingContext Context { get; set; } ISurrogateSelector SurrogateSelector { get; set; } object Deserialize(Stream serializationStream); void Serialize(Stream serializationStream, object graph); }
The System.Runtime.Remoting.Messaging.IRemotingFormatter interface (which is leveraged internally by the .NET remoting layer) overloads the Serialize() and Deserialize() members into a manner more appropriate for distributed persistence. Note that IRemotingFormatter derives from the more general IFormatter interface:
public interface IRemotingFormatter : IFormatter { object Deserialize(Stream serializationStream, HeaderHandler handler); void Serialize(Stream serializationStream, object graph, Header[] headers); }
Although you might not need to interact directly with these interfaces for most of your serialization endeavors, recall that interface-based polymorphism allows you to hold an instance of BinaryFormatter or SoapFormatter using an IFormatter reference. Therefore, if you wish to build a method that can serialize an object graph using either of these classes, you could write the following:
static void SerializeObjectGraph(IFormatter itfFormat, Stream destStream, object graph) { itfFormat.Serialize(destStream, graph); }
The most obvious difference among the three formatters is how the object graph is persisted to the stream (binary, SOAP, or XML). You should also be aware of a few more subtle points of distinction; specifically, how the formatters contend with type fidelity. When you use the BinaryFormatter type, it will persist not only the field data of the objects in the object graph, but also each type’s fully qualified name and the full name of the defining assembly (name, version, public key token, and culture). These extra points of data make the BinaryFormatter an ideal choice when you wish to transport objects by value (e.g., as a full copy) across machine boundaries for .NET-centric applications.
The SoapFormatter persists traces of the assembly of origin through the use of an XML namespace. For example, recall the Person type earlier in this chapter. If this type were persisted as a SOAP message, you would find that the opening element of Person is qualified by the generated xmlns. Consider this partial definition, paying special attention to the a1 XML namespace:
<a1:Person id="ref-1" xmlns:a1= "http://schemas.microsoft.com/clr/nsassem/SimpleSerialize/MyApp%2C%20 Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull"> <isAlive>true</isAlive> <personAge>21</personAge> <fName id="ref-3"></fName> </a1:Person>
However, the XmlSerializer does not attempt to preserve full type fidelity; therefore, it does not record the type’s fully qualified name or assembly of origin. This might seem like a limitation at first glance, but XML serialization is used by classic .NET web services, which can be called from clients on any platform (not just .NET). This means that there is no point serializing full .NET type metadata. Here is a possible XML representation of the Person type:
<?xml version="1.0"?> <Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <isAlive>true</isAlive> <PersonAge>21</PersonAge> <FirstName /> </Person>
If you wish to persist an object’s state in a manner that can be used by any operating system (e.g., Windows XP, Mac OS X, and various Linux distributions), application framework (e.g., .NET, Java Enterprise Edition, and COM), or programming language, you do not want to maintain full type fidelity because you cannot assume all possible recipients can understand .NET-specific data types. Given this, SoapFormatter and XmlSerializer are ideal choices when you wish to ensure as broad a reach as possible for the persisted tree of objects.